
//-----------------------------------------------------------------------------
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Drawing;


namespace SdmxMl.UC
{
	/// <summary>
	/// Supported types in NumTextBox control.
	/// </summary>
	public enum NumEditType
	{
        None,
		/// Edit a Currency value.
		Currency,
		/// Edit a Decimal value.
		Decimal,
		/// Edit a Single value.
		Single,
		/// Edit a Double value.
		Double,
		/// Edit a SmallInteger value.
		SmallInteger,
		/// Edit a Integer value.
		Integer,
		/// Edit a LargeInteger value.
		LargeInteger,
		/// Edit av AlphaNum value.
		AlphaNum,
        /// SDMX id
        SdmxIdType,
	}

	/// <summary>
	/// Numeric data entry textbox control.
	/// </summary>
	public class NumTextBox : TextBox
	{
        private int maxLenBeforeWarn;
		private NumEditType inpType;
		private const string REGEXPATTERN_ALPHANUMERIC = @"[_0-9a-zA-Z]";
        static Regex regexSDMX = new Regex("^([A-Z]|[a-z]|\\*|@|[0-9]|_|$|\\-)*$", RegexOptions.Compiled);

		/// <summary>
		/// Construct a NumTextBox control.
		/// </summary>
		public NumTextBox()
		{
			// set default input type
			inpType = NumEditType.Integer;

			// NOTE: Existing context menu allows Paste command, with no known
			// method to intercept. Only option is to reset to empty.
			// (setting to Null doesn't work)
			this.ContextMenu = new ContextMenu();
		}

		/// <summary>
		/// Define the input type for the control.
		/// </summary>
		[Description("Sets the numeric type allowed"), Category("Behavior")]
		public NumEditType InputType
		{
			get { return inpType; }
			set { inpType = value; }
		}

        /// <summary>
        /// Define the Mal length before warning.
        /// </summary>
        [Description("Define the Mal length before warning."), Category("Behavior")]
        [DefaultValue(0)]
        public int MaxLenWarn
        {
            get { return maxLenBeforeWarn; }
            set { maxLenBeforeWarn = value; }
        }
        /// <summary>
		/// The text for the control.
		/// </summary>
		/// <remarks>
		/// Only valid text can be assigned to the control.
		/// </remarks>
		public override string Text
		{
			get { return base.Text; }
			set
			{
                if (IsValid(value, true))
                    base.Text = value;
                else
                    base.Text = string.Empty;
                CheckLen(base.Text);
			}
		}
        private bool CheckLen(string txt)
        {
            if (maxLenBeforeWarn > 0 && txt.Length > maxLenBeforeWarn)
                this.BackColor = Color.Red;
            else
            {
                this.BackColor = Color.White;
            }
            return this.BackColor == Color.Red;
        }
		// this method validates the ENTIRE string, not each character
		// Rev 1: Added bool user param. This bypasses preliminary checks
		// that allow -, this is used by the OnLeave event to prevent.
		private bool IsValid(string val, bool user)
		{
			bool ret = true;

			if (val == null || val.Length == 0)
				return ret;
			
			if (user)
			{
				// allow first char == '-' for numeric
				if (val.Equals("-") && inpType != NumEditType.AlphaNum)
					return ret;
			}

			// parse into dataType, errors indicate invalid value
			// NOTE: parsing also validates data type min/max
			try
			{
				switch (inpType)
				{
                    case NumEditType.SdmxIdType:
                        ret = regexSDMX.IsMatch(val) && !CheckLen(val);
                        break;
                    
                    case NumEditType.AlphaNum:
						Regex rexAlphaNumeric = new Regex(REGEXPATTERN_ALPHANUMERIC, RegexOptions.Compiled);
						ret = ((rexAlphaNumeric.Matches(val).Count > 0) && (rexAlphaNumeric.Matches(val).Count) == val.Length);
						break;

					case NumEditType.Currency:
						decimal.Parse(val);

						int pos = val.IndexOf(".");
						if (pos != -1)
							ret = val.Substring(pos).Length <= 3;	// 2 decimals + "."
						break;

					case NumEditType.Single:
						float.Parse(val);
						break;
					
					case NumEditType.Double:
						double.Parse(val);
						break;
					
					case NumEditType.Decimal:
						decimal.Parse(val);
						break;
					
					case NumEditType.SmallInteger:
						short.Parse(val);
						break;
					
					case NumEditType.Integer:
						int.Parse(val);
						break;
					
					case NumEditType.LargeInteger:
						long.Parse(val);
						break;
					case NumEditType.None :
                        ret = true;
                        break;
					default:
						throw new ApplicationException();
				}
			}
			catch
			{
				ret = false;
			}
			return ret;
		}

		/// <summary>
		/// Process the command key message.
		/// </summary>
		/// <param name="msg">The message.</param>
		/// <param name="keyData">The key data.</param>
		/// <returns>false to allow further processing else true.</returns>
		/// <remarks>
		/// This override traps Ctrl-V paste and prevents invalid values.
		/// </remarks>
		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
		{
			// trap Ctrl-V paste and prevent invalid values
			// return false to allow further processing
			if (keyData == (Keys)Shortcut.CtrlV || keyData == (Keys)Shortcut.ShiftIns)
			{
				IDataObject iData = Clipboard.GetDataObject();
 
				// assemble new string and check IsValid
				string newText;
				newText = base.Text.Substring(0, base.SelectionStart)
					+ (string)iData.GetData(DataFormats.Text)
					+ base.Text.Substring(base.SelectionStart + base.SelectionLength);

				// check if data to be pasted is convertable to inputType
				if (!IsValid(newText, true))
					return true;
			}
			return base.ProcessCmdKey(ref msg, keyData);
		}

		/// <summary>
		/// Called when the control leave his focus.
		/// </summary>
		/// <param name="e">The event argument.</param>
		/// <remarks>
		/// This override checks for valid empty or leading zeros input.
		/// </remarks>
		protected override void OnLeave(EventArgs e)
		{
			// handle - and leading zeros input since KeyPress handler must allow this
			if (base.Text.Length != 0)
			{
				if (!IsValid(base.Text, false))
					base.Text = string.Empty;
				else if (inpType != NumEditType.AlphaNum &&
                    inpType != NumEditType.SdmxIdType && Double.Parse(base.Text) == 0)	// this used for -0, 000 and other strings
					base.Text = "0";
			}
			base.OnLeave(e);
		}

		/// <summary>
		/// Callen on every pressed keys.
		/// </summary>
		/// <param name="e">The key press event argument.</param>
		protected override void OnKeyPress(KeyPressEventArgs e)
		{
			// assemble new text with new KeyStroke
			// and pass to validation routine

			// NOTES:
			// 1) Delete key is NOT passed here
			// 2) control passed here after ProcessCmdKey() is run

			char c = e.KeyChar;
			if (!Char.IsControl(c))	// not sure about this?? nothing in docs about what is Control char??
			{
				// prevent spaces
				if (c.ToString() == " ")
				{
					e.Handled = true;
					return;
				}

				string newText = base.Text.Substring(0, base.SelectionStart)
					+ c.ToString() + base.Text.Substring(base.SelectionStart + base.SelectionLength);
				
				if (!IsValid(newText, true))
					e.Handled = true;
			}
			base.OnKeyPress(e);
            CheckLen(base.Text);
		}
	}
}
